package com.allesblinkt.mucom.client;

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.UIManager;

import mathematik.Vector2f;

import com.allesblinkt.mucom.common.ClientItem;
import com.allesblinkt.mucom.common.Constants;
import com.allesblinkt.mucom.common.ContentItem;
import com.allesblinkt.mucom.common.StatusRepresentation;
import com.allesblinkt.mucom.common.Utils;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PImage;

import processing.opengl.*;

@SuppressWarnings("unused")
public class ClientApp extends PApplet implements Constants {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private int myColor;

	private NetController netController;

	private NotifyRepresentation notifyRepresentation;

	private Logger logger;

	private ClientItem client;

	private Organism organism;

	private PImage foo;

	private int prevIndex = 0;

	private ScreenTiler screenTiler;

	private PFont daFont;

	private int windowX;

	private int windowY;

	private boolean isStopped = false;

	private double lastInteraction;
	
	private JFileChooser dirChooser;

	private StatusRepresentation statusRepresentation;

	private double lastCryForAttention;
	
	private int flashCount = 0;
	
	private double enterTime;

	private PImage flashImage;
	
	private boolean isFed = false;

	public ClientApp(Frame theFrame) {
		this.frame = theFrame;
	}

	public void setup() {

		for (int i = 0; i < 10; i++) {
			size(WINDOW_WIDTH, WINDOW_HEIGHT, OPENGL);
		}

		frameRate(FRAMERATE);
		smooth();

		logger = Logger.getLogger(CLIENT_LOGGER);
		logger.setLevel(Level.ALL);

			
		
		client = new ClientItem();
		client.setHash(Float.toString(random(Float.MAX_VALUE)));

		client.setHostname(Utils.getUserName());
		client.setOs(Utils.getOs());

		netController = new NetController(client);

		screenTiler = new ScreenTiler(this);

		myColor = color(random(0, 255), random(0, 255), random(0, 255));

		notifyRepresentation = new NotifyRepresentation(this);
		
		statusRepresentation = new StatusRepresentation(this);
		statusRepresentation.setActiveClientHash(client.getHash());

		windowX = frame.getLocationOnScreen().x;
		windowY = frame.getLocationOnScreen().y;
		
		registerPost(this);
		
		
		dirChooser = new JFileChooser();
		dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		dirChooser.setDialogTitle("Choose directory to save file...");

		
		
		

		DropTargetAdapter dropHandler = new DropTargetAdapter() {

			@Override
			public void dragEnter(DropTargetDragEvent dtde) {
				stopOrganism();
				
				List<DataFlavor> flavors = dtde.getCurrentDataFlavorsAsList();

				for (DataFlavor dataFlavor : flavors) {
					System.out.println(dataFlavor);
				}

				if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {

					dtde.acceptDrag(DnDConstants.ACTION_COPY);

					notifyRepresentation
							.setPermanent(NotifyState.UPLOAD_ALLOWED);
				}
				else {
					notifyRepresentation
							.setPermanent(NotifyState.UPLOAD_DENIED);


					dtde.rejectDrag();

				}

				dropActionChanged(dtde);

			}

			@Override
			public void dragExit(DropTargetEvent dte) {
				stopOrganism();

				notifyRepresentation.hide();
			
			}

		

			public void drop(DropTargetDropEvent dtde) {
				stopOrganism();

				
				int action = dtde.getDropAction();
				dtde.acceptDrop(action);

				if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
					try {

						List transferData = (List) dtde.getTransferable()
								.getTransferData(DataFlavor.javaFileListFlavor);

						for (Object file : transferData) {
							if (file instanceof File) {
								netController.addUpload((File) file);
								
								notifyRepresentation.setOnce(NotifyState.UPLOADING, NOTIFY_TIME);
								
								break;
							}
						}

						System.out.println(transferData.getClass());
					} catch (UnsupportedFlavorException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}

		};
		DropTarget dt = new DropTarget(frame, dropHandler);
		
		
		lastCryForAttention = System.currentTimeMillis();

		flashImage = loadImage("client/theft_back.png");
	}

	public void signalUserInteraction(){
		lastInteraction = System.currentTimeMillis();
	}
	
	public void stop() {

		// System.out.println("reached stop");
		try {
			netController.close();
		} catch (Exception e) {
			// Nevermind if it crashes hard
		}

		super.stop();

	}

	public void draw() {
		
		netController.tickle();
		screenTiler.tickle();
		
		
		double now = System.currentTimeMillis();

		if (now - lastInteraction > STOP_TIMEOUT * 1000 && isStopped) {
			isStopped = false;
			statusRepresentation.hideFileParticles();

		}
		
		if (now - lastInteraction > STOP_TIMEOUT * 1000) {
			
			if(now - lastCryForAttention > ATTENTION_TIMEOUT * 1000){
				notifyRepresentation.setOnce(NotifyState.ATTENTION, NOTIFY_TIME);
				lastCryForAttention = now;
			}

		}

		if (organism == null && netController.shouldEnter()) {
			System.gc();
			
			isFed = false;
			enterTime = now;
			
			netController.doEnter();
			organism = new Organism(this);

			statusRepresentation.reset();
			
			signalUserInteraction();
			
			showWindow();

			Vector<ClientItem> initClients = netController.clients();

			for (ClientItem client : initClients) {
				statusRepresentation.addClient(client);
			}

		}

		if (organism != null) {

			if (organism.getProgress() >= 1.0f) {
				netController.doRemove();
				organism = null;
				hideWindow();
			}
			
			if(netController.shouldLeave()){
				organism = null;
				hideWindow();
			}
			
		
		}

		
		if(frame.isVisible()){
			background(0);

			fill(255, 180, 230, 100);
			noStroke();

			if (organism != null) {

				if (!isStopped) {
					organism.continueTravel();

				} 
				
				
				if(now - enterTime > ANGRY_TIMEOUT * 1000 && !isFed && !isStopped){
					notifyRepresentation.setOnce(NotifyState.ANGRY, NOTIFY_TIME);
				}
				


				organism.animate();
				
				if(isStopped) {
					windowX = (int) organism.position().x+40;
					windowY = (int) organism.position().y+40;
				}

				if (organism.index != prevIndex) {
					windowX = (int) organism.position().x;
					windowY = (int) organism.position().y;
				}

			}

			Vector<ScreenTile> tiles = screenTiler.get(new Vector2f(windowX,
					windowY));

			for (ScreenTile tile : tiles) {
				if (tile.image != null) {
					rectMode(CORNER);
					image(tile.image, -windowX + tile.x, -windowY + tile.y);
					
				//	fill(random(255),random(255),random(255));
				//	rect( -windowX + tile.x, -windowY + tile.y, tile.image.width, tile.image.height);
				}
			}

			
	/* Flash screen */
			
	
			
			Vector2f centerOffset = new Vector2f(0, 0);

			if (organism != null) {
				pushMatrix();
				translate(-windowX + width / 2, -windowY + height / 2);
				
				centerOffset.set(organism.position().x - windowX , organism
						.position().y
						- windowY);
				organism.draw();

				statusRepresentation.mouseAt(windowX - width /2 + mouseX , windowY - height/2 + mouseY);
				statusRepresentation.moveTo(organism.position());
				statusRepresentation.draw();

				popMatrix();

			} else {
			
				
			}

			fill(255, 0, 0);

			imageMode(CORNER);

			if(flashCount > 0){
				flashCount--;
				tint(255, flashCount/30f * 255);
				image(flashImage, 0,0);
				noTint();
			}
			
			pushMatrix();

			translate(centerOffset.x + width / 2, centerOffset.y + height / 2);
			notifyRepresentation.draw();



			if (organism != null) {
				if (organism.index != prevIndex || isStopped) {
					prevIndex = organism.index;
					moveWindowTo(windowX, windowY);
				}
			}

			
			
			popMatrix();
		}
		
		
		if(!notifyRepresentation.isShown() && frame.isVisible() && organism == null){
			hideWindow();
		}

		



	}
	
	public void post(){
		
		double now = System.currentTimeMillis();

		if(now - enterTime > THEFT_TIMEOUT * 1000 && !isFed && organism != null ){
			stealScreen();
		}
	}

	private void stealScreen() {
		hideWindow();
		
		flashCount = 30;

		int minX = max(32,windowX);
		int minY = max(32,windowY);
		int maxX = (int) min(screen.width,windowX+ WINDOW_WIDTH);
		int maxY = (int) min(screen.height,windowY+WINDOW_HEIGHT);



		Rectangle rect = new Rectangle(minX, minY,  maxX-minX, maxY-minY);


		try {
			isFed = true;


			Robot robot = new Robot();
			BufferedImage image = robot.createScreenCapture(rect);  

			File temp = File.createTempFile("mucom-client", ".tmp");

			ImageIO.write(image, "png", temp);  
			

			netController.addUpload((File) temp, "screentheft.png");
			notifyRepresentation.setOnce(NotifyState.THEFT, NOTIFY_TIME);

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}  





		showWindow();
	}

	private void hideWindow() {
		frame.setVisible(false);
	}
	
	private void showWindow() {
		frame.setVisible(true);
	}
	
	
	public void moveWindowTo(int theX, int theY) {
		frame.setLocation(theX, theY);
	}
	
	
	public void stopOrganism(){
		signalUserInteraction();

		if (!isStopped) {
			isStopped = true;

			statusRepresentation.showFileParticles();
		} 
			
	}

	public void mouseClicked() {
		
		stopOrganism();
		

		
		if(isStopped){
				
				ContentItem clickedItem = statusRepresentation.getContentItemUnderMouse();
				
				if(clickedItem != null){
					takeContent(clickedItem);
					statusRepresentation.removeContentItem(clickedItem);

				}
		}
			
			
		

	}

	
	public void takeContent(ContentItem theContentItem){
		
		
		 int returnVal = dirChooser.showOpenDialog(this);

         if (returnVal == JFileChooser.APPROVE_OPTION) {
        	 
             File targetDir = dirChooser.getSelectedFile();
             //This is where a real application would open the file.
             logger.log(Level.INFO, "Opening: " + targetDir.getName() + "." + "\n");
             
             if(targetDir.isDirectory()){
                 notifyRepresentation.setOnce(NotifyState.DOWNLOADED, NOTIFY_TIME);
          		
                 String contentUrl = netController.getUrlForContentItem(theContentItem);
                 downloadFile(contentUrl, targetDir, theContentItem.getLocalName());
                 
         		netController.takeContent(theContentItem);
             }
        
        

         
         } else {
        	 logger.log(Level.INFO, "Open command cancelled by user." );
         }
         
         
         
         
		
		
		
	}
	
	
	public void downloadFile(String theUrl, File theDir, String theLocalName ){
		OutputStream out = null;
		URLConnection conn = null;
		InputStream  in = null;
		
		
		File outFile = new File(theDir, theLocalName);
		
		if(!outFile.exists()){
			try {
				URL url = new URL(theUrl);
				out = new BufferedOutputStream(
					new FileOutputStream(outFile));
				conn = url.openConnection();
				in = conn.getInputStream();
				byte[] buffer = new byte[1024];
				int numRead;
				long numWritten = 0;
				while ((numRead = in.read(buffer)) != -1) {
					out.write(buffer, 0, numRead);
					numWritten += numRead;
				}
			} catch (Exception exception) {
				exception.printStackTrace();
			} finally {
				try {
					if (in != null) {
						in.close();
					}
					if (out != null) {
						out.close();
					}
				} catch (IOException ioe) {
				}
			}
		} else {
			logger.log(Level.INFO, "File already exists.");
		}
		
		
	}

	public static void main(String[] args) {

	    try {
	        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
	      } catch(Exception e) {
	        System.out.println("Error setting native LAF: " + e);
	      }

		JFrame frame = new JFrame();

	    frame.addWindowListener(new WindowAdapter() {
	        public void windowClosing(WindowEvent e) {
	          System.exit(0);
	        }
	      });
		
		frame.setUndecorated(true);
		frame.setFocusable(false);

		frame.setAlwaysOnTop(true);

		frame.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
		

		frame.setBackground(new Color(0, 0, 0, 0));

		PApplet app = new ClientApp(frame);

		app.init();

		frame.add(app);

		frame.pack();

		frame.setVisible(true);

	}

}